module.exports = function(cloud,models,api){
  var db = cloud.database(),_ = db.command,$ = _.aggregate;
  const log = cloud.logger();
  var items = new a.Item({max:10});
  items._do();
  a.DB = {
    db,
    _,
    $
  }
  a.DBCollect = function(next){
    items._push(function(_next){
      next(db,_next,_,$);
    })
  }
  a.Model = function(name,schemas){
    var _this = this;
    this.name = name;
    this.schemas = schemas;
    this.schemas._id = a.merge({label:''},this.schemas._id || {});
    this.schemas.createTime = a.merge({label:''},this.schemas.createTime || {});
    this.schemas.updateTime = a.merge({label:''},this.schemas.updateTime || {});
    this.schemas.state = a.merge({label:'',defaultValue:1},this.schemas.state || {});
    this.schemas.unitId = a.merge({label:''},this.schemas.unitId || {});
    for(var key in this.schemas) {
      this.schemas[key] = typeof this.schemas[key] == 'string' ? {label:this.schemas[key]} : this.schemas[key];
      this.schemas[key].id = key;
    }
    const cheakReg = /(and|or|exec|insert|select|update|count|\*|%|chr|mid|master|truncate|char|declare|delete|create|drop)\s/;
    this.checkSQL = function(sql,isQuery){
      try{
        if(typeof sql == 'string') {
          if(isQuery){
            if(cheakReg.test(sql)){
              return sql.replace(cheakReg,'');
            }else{
              if(/^\{.+\}$/.test(sql)){
                return JSON.parse(sql);
              }else{
                return sql;
              }
            }
          }else{
            if(!cheakReg.test(sql)){
              return sql;
            }
          }
        }else{
          return sql;
        }
      }catch(e){
        return isQuery ? '----------' : '';
      }
    }
    this.queryFormat = function(query){
      query = query || {};
      var o = {};
      for(var key in this.schemas) {
        var queryString = this.checkSQL(query[key],true);
        if(queryString != undefined) {
          if(typeof queryString == 'object'){
            var _queryString = null;
            for(var _key in queryString){
              if(_key === '$or'){
                var ors = [];
                for(var i = 0; i < queryString[_key].length; i ++){
                  for(var key_ in queryString[_key][i]){
                    var meth = key_.replace(/\$/,'');
                    meth = meth == 'ne' ? 'neq' : meth;
                    if(_[meth]) {
                      ors.push(_[meth](queryString[_key][i][key_]));
                    }else if(meth == 'regex' && typeof queryString[_key][i][key_] == 'string'){
                      ors.push(db.RegExp({
                        regexp: queryString[_key][i][key_],
                        options: 'gim',
                      }))
                    }
                  }
                }
                if(_queryString){
                  _queryString = _queryString.or(ors);
                }else{
                  _queryString = _.or(ors);
                }
              }else if(_key === '$regex' && typeof queryString[_key] == 'string'){
                _queryString = db.RegExp({
                  regexp: queryString[_key],
                  options: 'gim',
                })
              }else{
                var meth = _key.replace(/\$/,'');
                meth = meth == 'ne' ? 'neq' : meth;
                if(_[meth]){
                  if(_queryString){
                    _queryString = _queryString[meth](queryString[_key]);
                  }else{
                    _queryString = _[meth](queryString[_key]);
                  }
                }
              }
            }
            o[key] = _queryString;
          }else{
            o[key] = queryString;
          }
        }
      }
      return o;
    }
    this.$String = function(s,v){
      return s.replace('{key}',v);
    }
    this.projection = function(projection){
      if(!projection) return null;
      var rc = {};
      for(var key in projection){
        if(typeof projection[key] == 'string'){
          rc[projection[key]] = this.$String('${key}',key);
        }else{
          rc[key] = projection[key];
        }
      }
      return rc;
    }
    this.lookup = function(collect,joins){
      for(var key in joins){
        var pipeline = $.pipeline().match(_.expr($.and([$.eq([this.$String('${key}',joins[key].key || '_id'),this.$String('${key}','$localField')])])))
        var jointype = joins[key].type;
        if(joins[key].query) {
          jointype = jointype || 'INNER';
          var _query = a.models[joins[key].name].queryFormat(joins[key].query);
          if(_query) pipeline = pipeline.match(_query);
        }
        jointype = jointype || 'LEFT';
        if(joins[key].projection) pipeline.project(this.projection(joins[key].projection));
        var lastProject = {};
        lastProject[joins[key].name] = 0;
        jointype = jointype.toUpperCase();
        collect = collect.lookup({
          from:joins[key].name,
          let: {
            localField: this.$String('${key}',key)
          },
          pipeline: pipeline.done(),
          as:joins[key].name
        }).unwind({
          path:this.$String('${key}',joins[key].name),
          preserveNullAndEmptyArrays:jointype == 'LEFT'
        }).replaceRoot({
          newRoot: $.mergeObjects([ this.$String('${key}',joins[key].name), this.$String('${key}','$ROOT') ])
        }).project(lastProject)
      }
      return collect;
    }
    //查询
    this.find = function(query,next,args){
      args = args || {};
      if(typeof args == 'string' && api && api[args]) args = api[args];
      var collect = db.collection(_this.name);
      collect = collect.aggregate();
      collect = collect.match(_this.queryFormat(query));
      if(args.projection) collect = collect.project(this.projection(args.projection));
      if(args.join) collect = _this.lookup(collect,args.join);
      if(typeof next == 'function'){
        a.DBCollect(function(db,_next){
          collect.end().then(function(ret){
            next(ret.list[0] || {errorMsg:'未找到记录',nodata:true});
            _next();
          }).catch(err => {
            next({errorMsg:err.errMsg});
            _next();
          })
        })
      }else{
        return collect;
      }
    }
    //数量
    this.count = function(query,next,args){
      args = args || {};
      if(typeof args == 'string' && api && api[args]) args = api[args];
      query = query || {};
      if(query.state == undefined) query.state = 1;
      args.sort = args.sort || {createTime:-1};
      function buildCollect(){
        var _collect = db.collection(_this.name);
        _collect = _collect.aggregate().match(_this.queryFormat(query));
        if(args.projection) _collect = _collect.project(_this.projection(args.projection));
        if(args.join) _collect = _this.lookup(_collect,args.join);
        return _collect;
      }
      a.DBCollect(function(db,_next){
        var collect = buildCollect();
        collect.count('total').end().then(function(ret){
          var total = 0;
          if(ret.list && ret.list[0]) total = ret.list[0].total || 0;
          next({total});
          _next();
        }).catch(err => {
          next({errorMsg:err.errMsg});
          _next();
        });
      })
    }
    //列表
    this.list = function(query,next,args){
      args = args || {};
      if(typeof args == 'string' && api && api[args]) args = api[args];
      query = query || {};
      if(query.state == undefined) query.state = 1;
      args.sort = args.sort || {createTime:-1};
      function buildCollect(){
        var _collect = db.collection(_this.name);
        _collect = _collect.aggregate().match(_this.queryFormat(query));
        if(args.projection) _collect = _collect.project(_this.projection(args.projection));
        if(args.join) _collect = _this.lookup(_collect,args.join);
        return _collect;
      }
      if(typeof next == 'function'){
        a.DBCollect(function(db,_next){
          var collect = buildCollect();
          var limit = args.limit,skip = args.skip;
          if(query.pageSize || query.pageNum){
            collect.count('total').end().then(function(ret){
              var total = 0;
              if(ret.list && ret.list[0]) total = ret.list[0].total || 0;
              if(total > 0){
                limit = query.pageSize || 10;
                skip = ((query.pageNum || 1) - 1) * limit;
                collect = buildCollect();
                collect = collect.sort(args.sort);
                collect = collect.skip(skip);
                collect = collect.limit(-(-limit));
                collect.end().then(function(ret){
                  next({rows:ret.list,total});
                  _next();
                }).catch(err => {
                  next({errorMsg:err.errMsg});
                  _next();
                })
              }else{
                next({rows:[],total:0});
                _next();
              }
            }).catch(err => {
              next({errorMsg:err.errMsg});
              _next();
            });
          }else{
            collect = collect.sort(args.sort);
            if(skip) collect = collect.skip(skip);
            if(limit) collect = collect.limit(limit);
            collect.end().then(function(ret){
              next(ret.list);
              _next();
            }).catch(err => {
              next({errorMsg:err.errMsg});
              _next();
            })
          }
        })
      }else{
        return buildCollect();
      }
    }
    //数据验证
    this.dataFormat = function(data,update){
      var o = {},err = [];
      for(var key in (update ? data : this.schemas)) {
        //赋值
        if(key == '_id') {
          //continue;
        }else if(key == 'unitId' && data[key] != undefined && data[key] != null){
          o[key] = typeof data[key] == 'string' ? this.checkSQL(data[key]) : data[key];
        }else{
          if(this.schemas[key] && typeof this.schemas[key]){
            if(typeof data[key] == 'string') data[key] = this.checkSQL(data[key]);
            if(data[key] == undefined || data[key] == null){
              if(this.schemas[key].defaultValue != undefined) o[key] = this.schemas[key].defaultValue;
            }else{
              o[key] = data[key];
            }
            //验证
            if(this.schemas[key].rule){
              if(!this.schemas[key].rule.NotNull && !a.Verify.NotNull(o[key])[0]){
                //无需验证为空 但为空的情况
              }else{
                for(var k in this.schemas[key].rule){
                  var _rule = this.schemas[key].rule[k];
                  if(_rule != false){
                    var v = a.Verify[k](o[key],_rule);
                    if(!v[0]) err.push(this.schemas[key].label + v[1]);
                  }
                }
              }
            }
          }
        }
        if(!this.schemas[key]) continue;
        if(/^(sort|state|level|createTime|updateTime)$/.test(key)
          || this.schemas[key].type == 'switch' 
          || (this.schemas[key].data_type && this.schemas[key].data_type.indexOf('INT') > -1)
          || (this.schemas[key].attr && this.schemas[key].attr.type == 'number')  
          || (this.schemas[key].rule && (this.schemas[key].rule.PositiveInt 
            || this.schemas[key].rule.PositiveNum))){
          if(typeof o[key] == 'string') o[key] = -(-o[key]);
        }
      }
      if(err.length == 0){
        return o;
      }else{
        return {errorMsg:err.join(',')};
      }
    }
    //新增
    this.insert = function(data,next,args){
      args = args || {};
      if(typeof args == 'string' && api && api[args]) args = api[args];
      data = this.dataFormat(data);
      if(this.schemas.id) data.id = !data.id ? a.guid() : data.id;
      data.createTime = !data.createTime ? new Date().getTime() : data.createTime;
      data.state = 1;
      if(!data.errorMsg){
        var collect = db.collection(_this.name).add({data});
        if(typeof next == 'function'){
          a.DBCollect(function(db,_next){
            collect.then(ret => {
              if(!ret._id) {
                next({errorMsg:ret.errMsg || 'insert failed'});
              }else{
                data._id = ret._id;
                next(data);
              }
              _next();
            }).catch(err => {
              next({errorMsg:err.errMsg});
              _next();
            })
          })
        }else{
          return collect;
        }
      }else{
        if(typeof next == 'function'){
          next(data);
        }else{
          return data;
        }
      }
    }
    this.emptyQuery = function(query){
      var isEmpty = true;
      for(var _key in query) {
        if(query[_key]) {
          isEmpty = false;
          break;
        }
      }
      return isEmpty;
    }
    //更新
    this.update = function(query,data,next,args){
      args = args || {};
      if(typeof args == 'string' && api && api[args]) args = api[args];
      if(this.emptyQuery(query)) data = {errorMsg:'不能进行无条件更新!'};
      data = this.dataFormat(data,true);
      data.updateTime = !data.updateTime ? new Date().getTime() : data.updateTime;
      if(!data.errorMsg){
        var collect = db.collection(_this.name).where(_this.queryFormat(query)).update({data})
        if(typeof next == 'function'){
          a.DBCollect(function(db,_next){
            collect.then(ret => {
              next(ret);
              _next();
            }).catch(err => {
              next({errorMsg:err.errMsg});
              _next();
            })
          })
        }else{
          return collect;
        }
      }else{
        if(typeof next == 'function'){
          next(data);
        }else{
          return data;
        }
      }
    }
    //删除
    this.remove = function(query,next,args){
      if(this.emptyQuery(query)) {
        var data = {errorMsg:'不能进行无条件删除!'};
        if(typeof next == 'function') next(data);
        return data;
      }
      args = args || {};
      var collect = db.collection(_this.name).where(_this.queryFormat(query,true));
      if(args.real){
        collect = collect.remove({data:{state:0}});
        if(typeof next == 'function'){
          a.DBCollect(function(db,_next){
            collect.then(ret => {
              next(ret);
              _next();
            }).catch(err => {
              next({errorMsg:err.errMsg});
              _next();
            })
          })
        }else{
          return collect;
        }
      }else{
        collect = collect.update({data:{state:0}});
        if(typeof next == 'function'){
          a.DBCollect(function(db,_next){
            collect.then(ret => {
              next(ret);
              _next();
            }).catch(err => {
              next({errorMsg:err.errMsg});
              _next();
            })
          })
        }else{
          return collect
        }
      }
    }
  }
  a.models = {};
  for(var key in models) a.models[key] = new a.Model(key,models[key]);
  //路由解析
  a.TCBExpress = function(){
    var _this = this;
    this.routers = {};
    this.bindRouter = function(url,meth,next){
      url = url.replace(/\//g,'-');
      if(url.indexOf(':') > -1){
        url = url.split(':');
        var params = [];
        for(var i = 1; i < url.length; i ++) params.push(url[i]);
        this.routers[meth + url[0]] = {
          params,
          router:next
        }
      }else{
        this.routers[meth + url] = next;
      }
    }
    this.meths = {
      get:function(url,next){
        _this.bindRouter(url,'GET',next)
      },
      post:function(url,next){
        _this.bindRouter(url,'POST',next)
      },
      put:function(url,next){
        _this.bindRouter(url,'PUT',next)
      },
      delete:function(url,next){
        _this.bindRouter(url,'DELETE',next)
      }
    }
    this.router = function(next){
      next.call(this.meths);
    }
    this.bindRouter('/model/:name','GET',function(req,res){
      res.send(a.models[req.params.name].schemas);
    })
    this.bindRouter('/api/model/:name','GET',function(req,res){
      req.query._ = null;
      if (req.query.join) {
        req.sql = req.sql || {};
        req.sql.join = req.sql.join || {};
        var joins = req.query.join.split(',');
        for (var i = 0; i < joins.length; i++) {
          req.sql.join[joins[i] + 'Id'] = {
            name: joins[i],
            projection: {
              name: joins[i] + 'Name'
            }
          }
        }
        req.query.join = null;
      }
      if(req.query.id || req.query._id){
        a.models[req.params.name].find(req.query,function(ret){
          res.send(ret);
        },req.sql)
      }else{
        if(a.models[req.params.name].schemas.sort) {
          req.sql = req.sql || {};
          req.sql.sort = req.sql.sort || { sort:-1,createTime:1 };
        }
        if (req.unitId) req.query.unitId = req.query.unitId || {
          $regex: req.unitId
        };
        req.query.unitId = req.query.unitId == 'all' ? undefined : req.query.unitId;
        a.models[req.params.name].list(req.query,function(ret){
          res.send(ret);
        },req.sql)
      }
    })
    this.bindRouter('/count/:name','GET',function(req,res){
      req.query._ = null;
      if (req.unitId) req.query.unitId = req.query.unitId || {
        $regex: req.unitId
      };
      req.query.unitId = req.query.unitId == 'all' ? undefined : req.query.unitId;
      a.models[req.params.name].count(req.query,function(ret){
        res.send(ret);
      },req.sql)
    })
    this.bindRouter('/api/model/:name','POST',function(req,res){
      if (req.unitId) req.body.unitId = req.body.unitId || req.unitId;
      req.query.unitId = req.query.unitId == 'all' ? undefined : req.query.unitId;
      a.models[req.params.name].insert(req.body,function(ret){
        if (!ret.errorMsg) {
          for (var key in a.models[req.params.name].schemas) if (a.models[req.params.name].schemas[key].unModel && ret[key]) ret[key] = undefined;
        }
        res.send(ret);
      },req.sql);
    })
    this.bindRouter('/api/model/:name','PUT',function(req,res){
      a.models[req.params.name].update({_id:req.body._id},req.body,function(ret){
        res.send(ret);
      },req.sql);
    })
    this.bindRouter('/api/model/:name','DELETE',function(req,res){
      if (req.unitId) req.query.unitId = req.query.unitId || {
        $regex: req.unitId
      };
      req.query.unitId = req.query.unitId == 'all' ? undefined : req.query.unitId;
      a.models[req.params.name].remove(req.query,function(ret){
        res.send(ret);
      },req.sql);
    })
    this.bindRouter('/login','POST',function(req,res){
      req.body.status = 1;
      a.models.user.find(req.body,function(ret){
        if(!ret.errorMsg){
          ret.token = new Date().getTime();
          ret.userId = ret._id;
          ret.id = ret._id;
        }
        res.send(ret);
      });
    })
    this.bindRouter('/logout','GET',function(req,res){
      res.send({});
    })
    this.server = function(event, context,next){
      try{
        var router = event.router;
        if(event.platform != 'WeChatMiniApp' && router != 'POST-login'){
          var token = event.headers['token'];
          if(!token || new Date().getTime() - token > 24 * 60 * 60 * 1000){
            next({errorMsg:'token error',code:400});
            return false;
          }else{
            event.session = event.headers;
          }
        }else{
          event.session = event.headers;
        }
        event.unitId = event.query.unitId || event.headers.unitId;
        if(_this.routers[router] && typeof _this.routers[router] == 'function'){
          _this.routers[router](event,{
            send:function(data){
              next(data);
            }
          })
        }else{
          router = router.split('-');
          var _router = [];
          for(var i = 0; i < router.length; i ++){
            _router.push(router[i]);
            var _routerString = _router.join('-') + '-';
            if(_routerString && typeof _this.routers[_routerString] == 'object'){
              event.params = {};
              for(var j = 0; j < _this.routers[_routerString].params.length; j ++){
                event.params[_this.routers[_routerString].params] = router[i + j + 1];
              }
              _this.routers[_routerString].router(event,{
                send:function(data){
                  next(data);
                }
              })
              return;
            }
          }
          next({errorMsg:'can not found',code:404});
        }
      }catch(e){
        console.log(e);
        next({errorMsg:'system error',code:500});
      }
    }
  }
}